/*
 * UserNet  --  TCP/IP implementation in userspace
 * Copyright (C) 2010  Robert Millan
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stddef.h>
#include <stdio.h>
#include <errno.h>
#include <error.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>

#include "unet/misc.h"
#include "unet/ipc.h"

int
unet_ipc_listen ()
{
  int fd;
  struct sockaddr_un addr;

  fd = socket (AF_UNIX, SOCK_STREAM, 0);
  if (fd < 0)
    error (1, errno, "socket");

  addr.sun_family = AF_UNIX;

  COMPILE_TIME_ASSERT (sizeof (addr.sun_path) > sizeof (SERVER_SOCKET));
  strcpy (addr.sun_path, SERVER_SOCKET);

  if (unlink (addr.sun_path) == -1 && errno != ENOENT)
    error (1, errno, "unlink (%s)", addr.sun_path);

  if (bind (fd, (struct sockaddr *) &addr,
	    (offsetof (struct sockaddr_un, sun_path) + strlen (addr.sun_path) + 1)) < 0)
    error (1, errno, "bind");

  chmod (SERVER_SOCKET, 0666);

  if (listen (fd, 5) == -1)
    return -1;

  return fd;
}

int
unet_ipc_accept (int listen_fd, pid_t *pid, uid_t *uid)
{
  struct sockaddr_un addr;
  socklen_t len = sizeof (addr);
  int fd;

  fd = accept (listen_fd, (struct sockaddr *) &addr, &len);
  if (fd == -1)
    return -1;

  {
    struct ucred cred;
    socklen_t len = sizeof (cred);
    getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &cred, &len);
    *pid = cred.pid;
    *uid = cred.uid;
  }

  return fd;
}

ssize_t
unet_ipc_read (int fd, void *msg, size_t n)
{
  fd_set set;
  struct timeval timeout = { .tv_sec = 1, .tv_usec = 0 };
  size_t left;
  int status;

  FD_ZERO (&set);
  FD_SET (fd, &set);

  status = select (FD_SETSIZE, &set, NULL, &set, &timeout);
  if (status == -1)
    return -1;
  if (status == 0)
    {
      error (0, 0, "timeout reading from client");
      return -1;
    }

  for (left = n; left > 0;)
    {
      ssize_t len;
      len = read (fd, msg, left);
      if (len == -1)
	return -1;

      msg += len;
      left -= len;
    }

  return n;
}

ssize_t
unet_ipc_write (int fd, const void *msg, size_t n)
{
  fd_set set;
  struct timeval timeout = { .tv_sec = 1, .tv_usec = 0 };
  int status;

  FD_ZERO (&set);
  FD_SET (fd, &set);

  status = select (FD_SETSIZE, NULL, &set, &set, &timeout);
  if (status == -1)
    return -1;
  if (status == 0)
    {
      error (0, 0, "timeout writing to client");
      return -1;
    }

  return write (fd, msg, n);
}
